require("config")
require("xml")
module("framexml", package.seeall)

local format = string.format
local insert = table.insert
local concat = table.concat
local open = io.open

local function stringify(s)
	if s == nil then
		return "nil"
	else
		return format("%q", s)
	end
end

local function BaseFrameConversion(entry)
	local frame = {}
	insert(frame, format("RegisterFrame(%s, %s, %s, %s, %s, %s, function (frame)",
		entry.xarg.virtual and "true" or "false",
		stringify(entry.label),
		stringify(entry.xarg.name),
		entry.xarg.parent and stringify(entry.xarg.parent) or "parent",
		stringify(entry.xarg.inherits),
		entry.xarg.id and tostring(entry.xarg.id) or "nil"))
	if entry.xarg.protected then
		insert(frame, "SetFrameProtected(frame)")
	end
	if entry.xarg.id then
		insert(frame, format("frame:SetID(%d)", tonumber(entry.xarg.id)))
	end

	return frame
end

local BasePath

local function AppendFrameEntry(result, frame)
	insert(result, frame[1])
	for i = 2, #frame do
		insert(result, "\t"..frame[i])
	end
	insert(result, "end)")
end

DefaultScriptHandlerSignature = "function (self)"
ScriptHandlerSignature = {
	OnAttributeChanged = "function (self, name, value)",
	OnChar = "function (self, text)",
	OnCharComposition = "function (self, text)",
	OnClick = "function (self, button, down)",
	OnColorSelect = "function (self, r, g, b)",
	OnCursorChanged = "function (self, x, y, w, h)",
	OnDoubleClick = "function (self, button)",
	OnDragStart = "function (self, button)",
	OnEnter = "function (self, motion)",
	OnEvent = "function (self, event, ...)",
	OnHorizontalScroll = "function (self, offset)",
	OnHyperlinkClick = "function (self, link, text, button)",
	OnHyperlinkEnter = "function (self, link, text)",
	OnHyperlinkLeave = "function (self, link, text)",
	OnInputLanguageChanged = "function (self, language)",
	OnKeyDown = "function (self, key)",
	OnKeyUp = "function (self, key)",
	OnLeave = "function (self, motion)",
	OnMouseDown = "function (self, button)",
	OnMouseUp = "function (self, button)",
	OnMouseWheel = "function (self, delta)",
	OnMovieShowSubtitle = "function (self, text)",
	OnScrollRangeChanged = "function (self, xrange, yrange)",
	OnSizeChanged = "function (self, w, h)",
	OnTooltipAddMoney = "function (self, cost)",
	OnUpdate = "function (self, elapsed)",
	OnValueChanged = "function (self, value)",
	OnVerticalScroll = "function (self, offset)",
	PostClick = "function (self, button, down)",
	PreClick = "function (self, button, down)",
}

HandlingMap = {}

local warn_no_hmap = {}
local warn_not_handled = {}

local function get_number_arg(s, name)
	return tonumber(s.xarg[name] or 0) or 0
end

local function get_color_arg(s)
	return get_number_arg(s, "r"),
		get_number_arg(s, "g"),
		get_number_arg(s, "b"),
		s.xarg.a and get_number_arg(s, "a")
end

local function formatColorFunction(name, r, g, b, a)
	if a then
		return format("%s(%s, %s, %s, %s)", name, r, g, b, a)
	else
		return format("%s(%s, %s, %s)", name, r, g, b)
	end
end

function GetInsets(result, entry)
	if not entry or type(entry) ~= "table" then return 0, 0, 0, 0 end
	local left, right, top, bottom
	if entry[1] then
		local s = entry[1]
		if s.label == "AbsInset" then
			left = get_number_arg(s, "left")
			right = get_number_arg(s, "right")
			top = get_number_arg(s, "top")
			bottom = get_number_arg(s, "bottom")
		else
			if result then
				log("-- <%s> is UNSUPPORTED", s.label)
			end
			left = get_number_arg(s, "left")
			right = get_number_arg(s, "right")
			top = get_number_arg(s, "top")
			bottom = get_number_arg(s, "bottom")
		end
		return left, right, top, bottom
	else
		left = get_number_arg(entry, "left")
		right = get_number_arg(entry, "right")
		top = get_number_arg(entry, "top")
		bottom = get_number_arg(entry, "bottom")
		return left, right, top, bottom
	end
end

function GetDimensions(result, entry)
	if not entry or type(entry) ~= "table" then return 0, 0 end
	local x, y
	local s = entry[1]
	if s then
		if s.label == "AbsDimension" then
			x = get_number_arg(s, "x")
			y = get_number_arg(s, "y")
		else
			if result then
				log("-- <%s> is UNSUPPORTED", s.label)
			end
			x = get_number_arg(s, "x")
			y = get_number_arg(s, "y")
		end
		return x, y
	else
		x = get_number_arg(entry, "x")
		y = get_number_arg(entry, "y")
		return x, y
	end
end

function GetValue(result, entry)
	if not entry or type(entry) ~= "table" then return 0 end
	if entry[1] then
		local s = entry[1]
		local val
		if s.label == "AbsValue" then
			val = get_number_arg(s, "val")
		else
			if result then
				log("-- <%s> is UNSUPPORTED", s.label)
			end
			val = get_number_arg(s, "val")
		end
		return val
	else
		return get_number_arg(entry, "val")
	end
end

HandlingMap.Frame = {
	Size = function (result, entry)
		local x, y = GetDimensions(result, entry)
		insert(result, format("frame:SetWidth(%d)", tonumber(x)))
		insert(result, format("frame:SetHeight(%d)", tonumber(y)))
	end,
	Anchors = function (result, entry)
		for _, anchor in ipairs(entry) do
			if anchor.label == "Anchor" then
				local point = anchor.xarg.point
				local relativeTo = anchor.xarg.relativeTo
				local relativePoint = anchor.xarg.relativePoint
				local x, y = GetDimensions(result, anchor[1])
				insert(result, format("frame:SetPoint(%s, %s, %s, %d, %d)",
					stringify(point),
					stringify(relativeTo),
					stringify(relativePoint),
					tonumber(x),
					tonumber(y)))
			else
				log("EE Error in XML Conversion in <Anchors> : element <%s> found where only <Anchor> was expected.", anchor.label)
			end
		end
	end,
	Scripts = function (result, entry)
		for _, script in ipairs(entry) do
			if type(script) == "table" then
				local t = script.label
				local f = script.xarg["function"]
				if f then
					if f == "" then f = "nil" end
					insert(result, format("frame:SetScript(%s, %s)",
						stringify(t), f))
				else
					insert(result, format("frame:SetScript(%s, %s",
						stringify(t),
						ScriptHandlerSignature[t] or DefaultScriptHandlerSignature))
					for _, line in ipairs(script) do
						insert(result, "\t"..line:trim())
					end
					insert(result, "end)")
				end
			end
		end
	end,
	Frames = function (result, entry)
		for _, frame in ipairs(entry) do
			if type(frame) == "table" then
				local conv = FrameConversionMap[frame.label]
				if conv then
					local subresult = {}
					local res, err = pcall(conv, subresult, frame)
					if not res then
						log("EE Error in XML Conversion in <Frames> : \"%s\" while converting %s\n", err, frame.label)
					else
						insert(result, "do")
						insert(result, "\tlocal parent = frame")
						for _, line in ipairs(subresult) do
							insert(result, "\t"..line)
						end
						insert(result, "end")
					end
				else
					log("EE Error in XML Conversion in <Frames> : unexpected entry type : %s\n", frame.label)
				end
			end
		end
	end,
	Layers = function (result, entry)
		for _, layer in ipairs(entry) do
			if type(layer) == "table" then
				if layer.label ~= "Layer" then
					log("EE Error in XML Conversion in <Layers> : \"%s\" where Layer was expected.\n", layer.label)
				end
				insert(result, format("layer = %s", stringify(layer.xarg.level)))
				for _, region in ipairs(layer) do
					local conv = RegionConversionMap[region.label]
					if conv then
						local subresult = {}
						local res, err = pcall(conv, subresult, region)
						if not res then
							log("EE Error in XML Conversion in <Layer> : \"%s\" while converting %s\n", err, region.label)
						else
							insert(result, "do")
							for _, line in ipairs(subresult) do
								insert(result, "\t"..line)
							end
							insert(result, "end")
						end
					else
						log("EE Error in XML Conversion in <Layer> : unexpected entry type : %s", tostring(region.label))
					end
				end
			end
		end
	end,
	Backdrop = function (result, entry)
		insert(result, "frame:SetBackdrop({")
		for _, n in ipairs({"bgFile", "edgeFile", "tile"}) do
			local v = entry.xarg[n]
			if v then
				insert(result, format("\t%s = %q,", n, v))
			end
		end
		local color, bordercolor
		for _, sub in ipairs(entry) do
			if type(sub) == "table" then
				if sub.label == "EdgeSize" then
					insert(result, format("\tedgeSize = %d,", GetValue(result, sub)))
				elseif sub.label == "TileSize" then
					insert(result, format("\ttileSize = %d,", GetValue(result, sub)))
				elseif sub.label == "BackgroundInsets" then
					local left, right, top, bottom = GetInsets(result, sub)
					insert(result, "\tinsets = {")
					insert(result, format("\t\tleft = %d,", left))
					insert(result, format("\t\tright = %d,", right))
					insert(result, format("\t\ttop = %d,", top))
					insert(result, format("\t\tbottom = %d,", bottom))
					insert(result, "\t},")
				elseif sub.label == "Color" then
					color = sub
				elseif sub.label == "BorderColor" then
					bordercolor = sub
				else
					log("EE Error in XML Conversion in <Backdrop> : unexpected entry type : %s", tostring(sub.label))
				end
			end
		end
		insert(result, "})")
		if color then
			insert(result, formatColorFunction("frame:SetBackdropColor", get_color_arg(color)))
		end
		if bordercolor then
			insert(result, formatColorFunction("frame:SetBackdropBorderColor", get_color_arg(bordercolor)))
		end
	end,
	HitRectInsets = function (result, entry)
		local left, right, top, bottom = GetInsets(result, entry)
		insert(result, format("frame:SetHitRectInsets(%d, %d, %d, %d)", left, right, top, bottom))
	end,
	ResizeBounds = function (result, entry)
		for _, sub in ipairs(entry) do
			if type(sub) == "table" then
				if sub.label == "minResize" then
					local x, y = GetDimensions(result, sub)
					insert(result, format("frame:SetMinResize(%d, %d)", x, y))
				elseif sub.label == "maxResize" then
					local x, y = GetDimensions(result, sub)
					insert(result, format("frame:SetMaxResize(%d, %d)", x, y))
				else
					log("EE Error in XML Conversion in <ResizeBounds> : unexpected entry type : %s", tostring(sub.label))
				end
			end
		end
	end,
	TitleRegion = function (result, entry)
		local ins = {}
		insert(ins, "frame:CreateTitleRegion()")
		insert(ins, "local frame = frame:GetTitleRegion()")
		if entry.xarg.setAllPoints then
			insert(ins, "frame:SetAllPoints()")
		end
		insert(result, "do")
		for _, line in ipairs(ins) do
			insert(result, "\t"..line)
		end
		insert(result, "end")

	end,
	Attributes = function (result, entry)
		for _, attr in ipairs(entry) do
			local name = attr.xarg.name
			local value = attr.xarg.value
			local atype = attr.xarg.type
			if name and value then
				if atype == "number" then
					insert(result, format("frame:SetAttribute(%q, %d)", name, value))
				else
					insert(result, format("frame:SetAttribute(%q, %q)", name, value))
				end
			end
		end
	end,
}

HandlingMap.Texture = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	TexCoords = function (result, entry)
		insert(result, format("frame:SetTexCoord(%s, %s, %s, %s)", entry.xarg.left, entry.xarg.right, entry.xarg.top, entry.xarg.bottom))
	end,
	Color = function (result, entry)
		insert(result, formatColorFunction("frame:SetVertexColor", get_color_arg(entry)))
	end,
}
HandlingMap.FontString = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	FontHeight = function (result, entry)
		insert(result, format("frame:SetTextHeight(%d)", GetValue(result, entry)))
	end,
	Color = HandlingMap.Texture.Color,
}

local function SetTextureHandler (name)
	return function (result, entry)
		insert(result, "do")
		insert(result, format("\tlocal texture = frame:CreateTexture(%s, nil, %s)",
			stringify(entry.xarg.name),
			stringify(entry.xarg.inherits)))
		if entry.xarg.parentKey then
			insert(result, format("\tframe[%s] = texture", stringify(entry.xarg.parentKey)))
		end
		if entry.xarg.file then
			insert(result, format("\ttexture:SetTexture(%s)", stringify(entry.xarg.file)))
		end
		if entry.xarg.alphaMode then
			insert(result, format("\ttexture:SetBlendMode(%s)", stringify(entry.xarg.alphaMode)))
		end
		insert(result, format("\tframe:Set%sTexture(texture)", name))
		insert(result, "\tdo")
		insert(result, "\t\tlocal frame = texture")
		local hmap = HandlingMap.Texture
		local has_anchor
		for _, p in ipairs(entry) do
			has_anchor = has_anchor or (p.label == "Anchors")
			local handler = hmap[p.label]
			if handler then
				handler(result, p)
			elseif not warn_not_handled[p.label] then
				warn_not_handled[p.label] = true
				log("EE UNHANDLED:%s:%s", entry.label, p.label)
			end
		end
		if not has_anchor then
			insert(result, "frame:SetAllPoints()")
		end
		insert(result, "\tend")
		insert(result, "end")
	end
end

HandlingMap.Button = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Layers = HandlingMap.Frame.Layers,
	Frames = HandlingMap.Frame.Frames,
	HitRectInsets = HandlingMap.Frame.HitRectInsets,
	Attributes = HandlingMap.Frame.Attributes,
	Backdrop = HandlingMap.Frame.Backdrop,
	ButtonText = function (result, entry)
		insert(result, "do")
		insert(result, format("\tlocal fontstring = frame:CreateFontString(%s, nil, %s)",
			stringify(entry.xarg.name),
			stringify(entry.xarg.inherits)))
		if entry.xarg.text then
			insert(result, format("\tfontstring:SetText(%s)", stringify(entry.xarg.text)))
		end
		if entry.xarg.justifyH then
			insert(result, format("\tfontstring:SetJustifyH(%s)", stringify(entry.xarg.justifyH)))
		end
		if entry.xarg.nonspacewrap then
			insert(result, "\tfontstring:SetNonSpaceWrap(true)")
		end
		insert(result, "end")
	end,
	NormalTexture = SetTextureHandler("Normal"),
	DisabledTexture = SetTextureHandler("Disabled"),
	PushedTexture = SetTextureHandler("Pushed"),
	HighlightTexture = SetTextureHandler("Highlight"),
	NormalFont = function (result, entry)
		insert(result, format("frame:SetNormalFontObject(%s)", stringify(entry.xarg.style)))
	end,
	DisabledFont = function (result, entry)
		insert(result, format("frame:SetDisabledFontObject(%s)", stringify(entry.xarg.style)))
	end,
	HighlightFont = function (result, entry)
		insert(result, format("frame:SetHighlightFontObject(%s)", stringify(entry.xarg.style)))
	end,
	PushedTextOffset = function (result, entry)
		local x, y = GetDimensions(result, entry)
		insert(result, format("frame:SetPushedTextOffset(%d, %d)", x, y))
	end,
}

HandlingMap.EditBox = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Frames = HandlingMap.Frame.Frames,
	Scripts = HandlingMap.Frame.Scripts,
	Layers = HandlingMap.Frame.Layers,
	FontString = function (result, entry)
		insert(result, format("frame:SetFontObject(%q)", entry.xarg.inherits))
	end,
	HitRectInsets = HandlingMap.Frame.HitRectInsets,
}

HandlingMap.WorldFrame = {
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	Layers = HandlingMap.Frame.Layers,
}

HandlingMap.GameTooltip = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	Layers = HandlingMap.Frame.Layers,
	Backdrop = HandlingMap.Frame.Backdrop,
}

HandlingMap.StatusBar = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Layers = HandlingMap.Frame.Layers,
	Frames = HandlingMap.Frame.Frames,
	HitRectInsets = HandlingMap.Frame.HitRectInsets,
	BarTexture = SetTextureHandler("StatusBar"),
	BarColor = function (result, entry)
		insert(result, formatColorFunction("frame:SetStatusBarColor", get_color_arg(entry)))
	end,
}

HandlingMap.Slider = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	HitRectInsets = HandlingMap.Frame.HitRectInsets,
	Backdrop = HandlingMap.Frame.Backdrop,
	Layers = HandlingMap.Frame.Layers,
	ThumbTexture = SetTextureHandler("Thumb"),
}

HandlingMap.ScrollFrame = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Frames = HandlingMap.Frame.Frames,
	Layers = HandlingMap.Frame.Layers,
	Scripts = HandlingMap.Frame.Scripts,
	Backdrop = HandlingMap.Frame.Backdrop,
	ScrollChild = function (result, entry)
		assert(#entry == 1)
		local frame  = entry[1]
		local conv = FrameConversionMap[frame.label]
		if conv then
			local subresult = {}
			local res, err = pcall(conv, subresult, frame)
			if not res then
				log("EE Error in XML Conversion in <ScrollChild> : \"%s\" while converting %s\n", err, frame.label)
			else
				insert(result, "do")
				insert(result, "\tlocal parent = frame")
				for _, line in ipairs(subresult) do
					insert(result, "\t"..line)
				end
				insert(result, "parent:SetScrollChild(frame)")
				insert(result, "end")
			end
		else
			log("EE Error in XML Conversion in <ScrollChild> : unexpected entry type : %s\n", frame.label)
		end
	end,
}

HandlingMap.CheckButton = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	HitRectInsets = HandlingMap.Frame.HitRectInsets,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	Layers = HandlingMap.Frame.Layers,
	NormalTexture = HandlingMap.Button.NormalTexture,
	PushedTexture = HandlingMap.Button.PushedTexture,
	DisabledTexture = HandlingMap.Button.DisabledTexture,
	HighlightTexture = HandlingMap.Button.HighlightTexture,
	CheckedTexture = SetTextureHandler("Checked"),
	DisabledCheckedTexture = SetTextureHandler("DisabledChecked"),
}

HandlingMap.MessageFrame = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	FontString = HandlingMap.EditBox.FontString,
}

HandlingMap.Minimap = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Frames = HandlingMap.Frame.Frames,
	Scripts = HandlingMap.Frame.Scripts,
}

HandlingMap.ColorSelect = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	Backdrop = HandlingMap.Frame.Backdrop,
	Layers = HandlingMap.Frame.Layers,
	ColorWheelTexture = SetTextureHandler("ColorWheel"),
	ColorWheelThumbTexture = SetTextureHandler("ColorWheelThumb"),
	ColorValueTexture = SetTextureHandler("ColorValue"),
	ColorValueThumbTexture = SetTextureHandler("ColorValueThumb"),
}

HandlingMap.Cooldown = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
}

HandlingMap.Model = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
}

HandlingMap.TabardModel = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
}

HandlingMap.PlayerModel = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
}

HandlingMap.DressUpModel = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
}

HandlingMap.TaxiRouteFrame = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
}

HandlingMap.ScrollingMessageFrame = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Layers = HandlingMap.Frame.Layers,
	TitleRegion = HandlingMap.Frame.TitleRegion,
	Scripts = HandlingMap.Frame.Scripts,
	Frames = HandlingMap.Frame.Frames,
	FontString = HandlingMap.EditBox.FontString,
	ResizeBounds = HandlingMap.Frame.ResizeBounds,

}

HandlingMap.SimpleHTML = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	FontString = function (result, entry) -- @FIXME
		insert(result, format("frame:SetFontObject(%s)", stringify(entry.xarg.inherits)))
	end,
}

HandlingMap.MovieFrame = {
	Size = HandlingMap.Frame.Size,
	Anchors = HandlingMap.Frame.Anchors,
	Layers = HandlingMap.Frame.Layers,
	Scripts = HandlingMap.Frame.Scripts,
}

FrameConversionMap = {
	Frame = function (result, entry)
		local frame = { format("RegisterFrame(%s, %s, %s, %s, %s, %s, function (frame)",
			entry.xarg.virtual and "true" or "false",
			stringify(entry.label),
			stringify(entry.xarg.name),
			entry.xarg.parent and stringify(entry.xarg.parent) or "parent",
			stringify(entry.xarg.inherits),
			entry.xarg.id and tostring(entry.xarg.id) or "nil")
		}
		if entry.xarg.parentKey then
			insert(frame, format("frame:GetParent()[%s] = frame", stringify(entry.xarg.parentKey)))
		end
		if entry.xarg.protected then
			insert(frame, "SetFrameProtected(frame)")
		end
		if entry.xarg.id then
			insert(frame, format("frame:SetID(%d)", tonumber(entry.xarg.id)))
		end
		if entry.xarg.hidden then
			insert(frame, "frame:Hide()")
		end
		if entry.xarg.enableMouse then
			insert(frame, "frame:EnableMouse(true)")
		end
		if entry.xarg.toplevel then
			insert(frame, "frame:SetToplevel(true)")
		end
		if entry.label == "EditBox" and entry.xarg.numeric then
			insert(frame, "frame:SetNumeric(true)")
		end
		if entry.xarg.frameStrata then
			insert(frame, format("frame:SetFrameStrata(%s)", stringify(entry.xarg.frameStrata)))
		end
		if entry.xarg.setAllPoints then
			insert(frame, "frame:SetAllPoints()")
		end
		if entry.xarg.allowMouseover then
			insert(frame, format("frame:SetAllowMouseover(%s)", entry.xarg.allowMouseover == "true" and "true" or "false"))
		end
		if entry.xarg.disabled then
			if entry.xarg.disabled == "true" then
				insert(frame, "frame:Disable()")
			end
		end
		local hmap = HandlingMap[entry.label]
		if not hmap then
			if not warn_no_hmap[entry.label] then
				warn_no_hmap[entry.label] = true
				log("EE NO_HANDLING_MAP:%s", entry.label)
			end
			hmap = HandlingMap.Frame
		end
		for _, p in ipairs(entry) do
			if p.label then
				local handler = hmap[p.label]
				if handler then
					handler(frame, p)
				elseif not warn_not_handled[p.label] then
					warn_not_handled[p.label] = true
					log("EE UNHANDLED:%s:%s", entry.label, p.label)
				end
			else
				log("EE UNHANDLED TEXT in:%s", entry.label)
			end
		end
		AppendFrameEntry(result, frame)
	end,
}

for _, t in ipairs({
		"Button", "ScrollFrame", "ColorSelect", "GameTooltip", "StatusBar",
		"CheckButton", "ScrollingMessageFrame", "EditBox", "Slider",
		"WorldFrame", "Model", "MessageFrame", "Cooldown", "Minimap",
		"TaxiRouteFrame", "PlayerModel", "TabardModel", "DressUpModel",
		"SimpleHTML", "MovieFrame",

	}) do
	FrameConversionMap[t] = FrameConversionMap.Frame
end

VirtualRegionConversionMap = {
	FontString = function (result, entry)
		assert(entry.xarg.virtual)
		local hmap = HandlingMap[entry.label]
		if not hmap then
			if not warn_no_hmap[entry.label] then
				warn_no_hmap[entry.label] = true
				log("EE NO_HANDLING_MAP:%s", entry.label)
			end
			hmap = HandlingMap.Frame
		end
		local region = {}
		if entry.label == "Texture" and entry.xarg.file then
			insert(region, format("frame:SetTexture(%q)", entry.xarg.file))
		end
		for _, p in ipairs(entry) do
			local handler = hmap[p.label]
			if handler then
				handler(region, p)
			elseif not warn_not_handled[p.label] then
				warn_not_handled[p.label] = true
				log("EE UNHANDLED:%s:%s", entry.label, p.label)
			end
		end
		assert(not entry.xarg.parentKey)
		insert(result, format("RegisterFrame(true, %s, %s, %s, %s, nil, function (frame)",
				stringify(entry.label),
				stringify(entry.xarg.name),
				stringify(entry.xarg.parent),
				stringify(entry.xarg.inherits)))
		for _, line in ipairs(region) do
			insert(result, "\t"..line)
		end
		insert(result, "end)")
	end,
}

VirtualRegionConversionMap.Texture = VirtualRegionConversionMap.FontString

RegionConversionMap = {
	FontString = function (result, entry)
		local region = {
			"local parent = frame",
			format("local frame = parent:CreateFontString(%s, layer, %s)",
				stringify(entry.xarg.name),
				stringify(entry.xarg.inherits))
		}
		if entry.xarg.parentKey then
			insert(region, format("parent[%s] = frame", stringify(entry.xarg.parentKey)))
		end
		if entry.xarg.hidden then
			insert(region, "frame:Hide()")
		end
		if entry.xarg.text then
			insert(region, format("frame:SetText(_G[%q] or %q)", entry.xarg.text, entry.xarg.text))
		end
		local hmap = HandlingMap.FontString
		local has_anchor
		for _, p in ipairs(entry) do
			has_anchor = has_anchor or (p.label == "Anchors")
			local handler = hmap[p.label]
			if handler then
				handler(region, p)
			elseif not warn_not_handled[p.label] then
				warn_not_handled[p.label] = true
				log("EE UNHANDLED:%s:%s", entry.label, p.label)
			end
		end
		if not has_anchor then
			insert(region, "frame:SetAllPoints()")
		end
		for _, line in ipairs(region) do
			insert(result, line)
		end
	end,
	Texture = function (result, entry)
		local region = {
			"local parent = frame",
			format("local frame = parent:CreateTexture(%s, layer, %s)",
				stringify(entry.xarg.name),
				stringify(entry.xarg.inherits))
		}
		if entry.xarg.parentKey then
			insert(region, format("parent[%s] = frame", stringify(entry.xarg.parentKey)))
		end
		if entry.xarg.hidden then
			insert(region, "frame:Hide()")
		end
		if entry.xarg.file then
			insert(region, format("frame:SetTexture(%q)", entry.xarg.file))
		end
		if entry.xarg.alphaMode then
			insert(region, format("frame:SetBlendMode(%q)", entry.xarg.alphaMode))
		end
		local hmap = HandlingMap.Texture
		local has_anchor
		for _, p in ipairs(entry) do
			has_anchor = has_anchor or (p.label == "Anchors")
			local handler = hmap[p.label]
			if handler then
				handler(region, p)
			elseif not warn_not_handled[p.label] then
				warn_not_handled[p.label] = true
				log("EE UNHANDLED:%s:%s", entry.label, p.label)
			end
		end
		if not has_anchor then
			insert(region, "frame:SetAllPoints()")
		end
		for _, line in ipairs(region) do
			insert(result, line)
		end
	end,
}

HandlingMap.Font = {
	FontHeight = function (result, entry)
		insert(result, format("height = %d", GetValue(result, entry)))
	end,
	Shadow = function (result, entry)
		for _, p in ipairs(entry) do
			if p.label == "Offset" then
				local x, y = GetDimensions(result, p)
				insert(result, format("font:SetShadowOffset(%s, %s)", x, y))
			elseif p.label == "Color" then
				insert(result, formatColorFunction("font:SetShadowColor", get_color_arg(p)))
			else
				log("EE UNHANDLED:%s:%s", entry.label, p.label)
			end
		end
	end,
	Color = function (result, entry)
		insert(result, formatColorFunction("font:SetTextColor", get_color_arg(entry)))
	end,
}

OtherConversionMap = {
	Script = function (result, entry)
		if entry.xarg.file then
			local file = format("%s/%s", BasePath, entry.xarg.file:gsub("\\", "/"))
			insert(result, format("dofile(%q)", file))
		end
		if not entry.empty then
			insert(result, "do")
			for _, line in ipairs(entry) do
				insert(result, line)
			end
			insert(result, "end")
		end
	end,
	Font = function (result, entry)
		assert(entry.xarg.virtual)
		local font = {}
		insert(font, format("RegisterFont(%s, %s, function (font)",
			stringify(entry.xarg.name), stringify(entry.xarg.inherits)))
		insert(font,"local file, height, flags = font:GetFont()")
		if entry.xarg.font then
			insert(font, format("file = %q", entry.xarg.font))
		end
		if entry.xarg.outline then
			insert(font, format("flags = %q", entry.xarg.outline))
		end
		if entry.xarg.justifyH then
			insert(font, format("font:SetJustifyH(%q)", entry.xarg.justifyH))
		end
		if entry.xarg.justifyV then
			insert(font, format("font:SetJustifyV(%q)", entry.xarg.justifyV))
		end
		for _, p in ipairs(entry) do
			if p.label then
				local handler = HandlingMap.Font[p.label]
				if handler then
					handler(font, p)
				elseif not warn_not_handled[p.label] then
					warn_not_handled[p.label] = true
					log("EE UNHANDLED:%s:%s", entry.label, p.label)
				end
			else
				log("EE UNHANDLED TEXT in:%s", entry.label)
			end
		end
		insert(font, "font:SetFont(file, height, flags)")
		AppendFrameEntry(result, font)
	end,
	Include = function (result, entry)
		if entry.xarg.file then
			local file = format("%s/%s", BasePath, entry.xarg.file:gsub("\\", "/"))
			local f, err = xml.parsefile(file)
			if not f then
				log("EE Error including file : %s", err)
				insert(result, format("-- Unable to include %s : %s", file, err))
			else
				local d = convert(file, f, true)
				insert(result, d)
			end
		end
	end,
}

-- OtherConversionMap.script = OtherConversionMap.Script

ConversionMap = {
	Ui = setmetatable({}, {
		__index = function (tbl, key)
			return FrameConversionMap[key] or OtherConversionMap[key] or VirtualRegionConversionMap[key]
		end,
		__newindex = function (tbl, key, value)
		end,
	}),
	Bindings = {
		Binding = function (result, entry)
			local header = "function(keystate)"
			if  entry.xarg.joystick == "true" then
				if entry.xarg.pressure == "true" then
					header = "function(keystate, pressure)"
				elseif entry.xarg.angle == "true" then
					header = "function(keystate, pressure, angle, precision)"
				end
			end
			insert(result, format("DeclareBinding(%q, %s, %s", entry.xarg.name, entry.xarg.runOnUp or "nil", header))
			for _, line in ipairs(entry) do
				insert(result, line)
			end
			insert(result, "end)")
		end,
		ModifiedClick = function (result, entry)
			insert(result, format("SetModifiedClick(%q, %s)", entry.xarg.action,
				entry.xarg.default and string.format("%q", entry.xarg.default) or "nil"))
		end,
	},
}

HEADER_COMMENT = [[
-- Note that the following functions are provided only when running the transformed XML
-- RegisterFrame(virtual, type, name, parent, inherits, id, callback)
-- RegisterFont(name, inherits, callback)
-- SetFrameProtected(frame)
-- DeclareBinding(name, runOnUp, callback)
-- dofile(name)
]]
HEADER = [[
local RegisterFrame = RegisterFrame
local RegisterFont = RegisterFont
local SetFrameProtected = SetFrameProtected
local DeclareBinding = DeclareBinding
local dofile = dofile
local parent, layer
]]

function convert(path, src, inner)
	local result = {format("-- Auto-generated from \"%s\"", path)}
	if not inner then
		insert(result, HEADER_COMMENT)
		insert(result, HEADER)
	end
	local prev_BasePath = BasePath
	BasePath = path:gsub("/[^/]*$", "")
	src = src[1]
	local map = ConversionMap[src.label]
	assert(map, "Unable to find conversion map for "..src.label)
	for _, entry in ipairs(src) do
		if type(entry) == "table" then
			local conv = map[entry.label]
			if conv then
				local res, err = pcall(conv, result, entry)
				if not res then
					log("EE Error in XML Conversion : \"%s\" while converting %s\n", err, entry.label)
				end
			else
				log("EE Error in XML Conversion : unexpected entry type : %s\n", entry.label)
			end
		end
	end
	insert(result, format("-- end of \"%s\"", path))
	result = concat(result, "\n")
	if config.XML_DUMP_CONVERSION then
		local tempconv = path:gsub(".*/", "tempconv/").."\.lua"
		-- print (format("%s => %s", path, tempconv))
		local f = open(tempconv, "w")
		f:write(result)
		f:close()
		if not inner and lualint then
			lualint.lint(tempconv, function (s, ...) log("\t"..s, ...) end)
		end
	end
	BasePath = prev_BasePath
	return result
end
